package cn.xiaoke.damowang_boot_test.util.auto_wrapper;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.interfaces.Join;
import com.baomidou.mybatisplus.core.conditions.interfaces.Nested;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;

import java.io.Serializable;
import java.lang.invoke.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class AutoWrapperUtil {

    private final static MethodHandles.Lookup lookup = MethodHandles.lookup();

    public static <T, W extends Join<W> & Nested<W, W>> Wrapper<T> createWrapper(Class<T> clazz, Object obj) {
        if (obj == null) {
            return Wrappers.emptyWrapper();
        }
        final LambdaQueryWrapper<T> lambdaQueryWrapper = Wrappers.lambdaQuery(clazz);
        final Map<String, GroupInfo<T, W>> groupInfoMap = initGroup(clazz, obj);

//        final List<SFunction<T, Object>> list = new ArrayList<>();

//        groupInfoMap.forEach((mapK, mapV) -> {
//            final boolean groupJoin = mapV.join;
//            if (groupJoin) {
//                lambdaQueryWrapper.or(groupWrap -> {
//                    mapV.map.forEach((columnK, columnV) -> {
//                        final boolean columnJoin = columnV.join;
//                        final List<WhereInfo> infoList = columnV.infoList;
//                        if (columnJoin) {
//                            groupWrap.or(columnWrap -> {
//                                infoList.forEach(info -> {
//                                    columnWrap.or();
//                                    final SFunction<T, Object> sFunction = createSFunction(clazz, info);
//                                    list.add(sFunction);
//                                    final Object fieldValue = info.fieldValue;
////                                    if (info.symbol.equals("=")){
//                                    columnWrap.eq(fieldValue != null, sFunction, info.fieldValue);
////                                    }
//                                });
//                            });
//                        } else {
//                            groupWrap.and(columnWrap -> {
//                                infoList.forEach(info -> {
//                                    final SFunction<T, Object> sFunction = createSFunction(clazz, info);
//                                    list.add(sFunction);
//                                    final Object fieldValue = info.fieldValue;
////                                    if (info.symbol.equals("=")){
//                                    columnWrap.eq(fieldValue != null, sFunction, info.fieldValue);
////                                    }
//                                });
//                            });
//                        }
//                    });
//                });
//            } else {
//                lambdaQueryWrapper.and(groupWrap -> {
//                    mapV.map.forEach((columnK, columnV) -> {
//                        final boolean columnJoin = columnV.join;
//                        final List<WhereInfo> infoList = columnV.infoList;
//                        if (columnJoin) {
//                            groupWrap.or(columnWrap -> {
//                                infoList.forEach(info -> {
//                                    columnWrap.or();
//                                    final SFunction<T, Object> sFunction = createSFunction(clazz, info);
//                                    list.add(sFunction);
//                                    final Object fieldValue = info.fieldValue;
////                                    if (info.symbol.equals("=")){
//                                    columnWrap.eq(fieldValue != null, sFunction, info.fieldValue);
////                                    }
//                                });
//                            });
//                        } else {
//                            groupWrap.and(columnWrap -> {
//                                infoList.forEach(info -> {
//                                    final SFunction<T, Object> sFunction = createSFunction(clazz, info);
//                                    list.add(sFunction);
//                                    final Object fieldValue = info.fieldValue;
////                                    if (info.symbol.equals("=")){
//                                    columnWrap.eq(fieldValue != null, sFunction, info.fieldValue);
////                                    }
//                                });
//                            });
//                        }
//                    });
//                });
//            }
//        });
        final List<GroupInfo<T, W>> sortList = groupInfoMap.values().stream().sorted(Comparator.comparingInt(group -> (group.outerJoin ? 1 : 0))).collect(Collectors.toList());
        sortList.forEach(group -> group.processWhere((W) lambdaQueryWrapper));
        return lambdaQueryWrapper;
    }

//    private static <T> void joinAndBrackets(LambdaQueryWrapper<T> lambdaQueryWrap, JoinAndCondition joinAndConditon) {
//        if (!joinAndConditon.getJoin()) {
//            and(lambdaQueryWrap, joinAndConditon);
//        } else {
//            or(lambdaQueryWrap, joinAndConditon);
//        }
//    }
//
//    @SuppressWarnings("unchecked")
//    private static <T> void and(LambdaQueryWrapper<T> lambdaQueryWrap, JoinAndCondition joinAndConditon) {
//        if (joinAndConditon.getNextFloor() || joinAndConditon.getBrackets()) {
//            lambdaQueryWrap.and(innerNested -> {
//                joinAndConditon.getInfos().forEach(info -> {
//                    joinAndBrackets(innerNested, info);
//                });
//            });
//        } else {
//            if (joinAndConditon instanceof WhereInfo) {
//                WhereInfo<T> info = (WhereInfo<T>) joinAndConditon;
////                final SFunction<T, Object> sFunction = createSFunction(info.clazz, info);
////                lambdaQueryWrap.eq(info.getCondition(), sFunction, info.fieldValue);
//                whereSwitch(lambdaQueryWrap,info);
//            } else {
//                final Collection<JoinAndCondition> infos = joinAndConditon.getInfos();
//                infos.forEach(info -> {
//                    joinAndBrackets(lambdaQueryWrap, info);
//                });
//            }
//        }
//    }
//
//    @SuppressWarnings("unchecked")
//    private static <T> void or(LambdaQueryWrapper<T> lambdaQueryWrap, JoinAndCondition joinAndConditon) {
//        if (joinAndConditon.getNextFloor() || joinAndConditon.getBrackets()) {
//            lambdaQueryWrap.or(innerNested -> {
//                joinAndConditon.getInfos().forEach(info -> {
//                    joinAndBrackets(innerNested, info);
//                });
//            });
//        } else {
//            if (joinAndConditon instanceof WhereInfo) {
//                WhereInfo<T> info = (WhereInfo<T>) joinAndConditon;
////                final SFunction<T, Object> sFunction = createSFunction(info.clazz, info);
////                lambdaQueryWrap.eq(info.getCondition(), sFunction, info.fieldValue);
//                lambdaQueryWrap.or();
//                whereSwitch(lambdaQueryWrap,info);
//            } else {
//                final Collection<JoinAndCondition> infos = joinAndConditon.getInfos();
//                infos.forEach(info -> {
//                    joinAndBrackets(lambdaQueryWrap, info);
//                });
//            }
//        }
//    }

    @SuppressWarnings("unchecked")
    private static <T, W extends Join<W> & Nested<W, W>> SFunction<T, Object> createSFunction(Class<T> clazz, WhereInfo<T, W> whereInfo) {
        try {
            final Method getMethod = whereInfo.getMethod;
            if (getMethod == null) throw new RuntimeException(whereInfo.column + "的get方法为null");
            final MethodHandle methodHandle = lookup.unreflect(getMethod);
            final CallSite getCallSite = LambdaMetafactory.altMetafactory(
                    lookup
                    , "apply"
                    , MethodType.methodType(SFunction.class)
                    , MethodType.methodType(Object.class, Object.class)
                    , methodHandle
                    , MethodType.methodType(Object.class, clazz)
                    , LambdaMetafactory.FLAG_SERIALIZABLE
                    , Serializable.class
            );
            return (SFunction<T, Object>) getCallSite.getTarget().invokeExact();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        log.error("createSFunction返回null,参数:{},{}", clazz, whereInfo);
        return null;
    }

    private static <T, W extends Join<W> & Nested<W, W>> Map<String, GroupInfo<T, W>> initGroup(Class<T> clazz, Object obj) {
        Class<?> dtoClazz = obj.getClass();
        final List<Field> anList1 = FieldUtils.getFieldsListWithAnnotation(dtoClazz, Wheres.class);
        final List<Field> anList2 = FieldUtils.getFieldsListWithAnnotation(dtoClazz, Where.class);
        final Map<String, GroupInfo<T, W>> map = new HashMap<>();

        anList1.forEach(field -> {
            final String fieldName = field.getName();
            final Wheres wheres = AnnotationUtils.findAnnotation(field, Wheres.class);
            wheresGroupAddInfo(clazz, obj, map, field, fieldName, wheres);
        });
        anList2.forEach(field -> {
            final String fieldName = field.getName();
            final String group = field.getName();
            final Object val = getFieldValue(obj, field);

            final GroupInfo<T, W> groupInfo = map.computeIfAbsent(group, GroupInfo::new);

            final Where where = AnnotationUtils.findAnnotation(field, Where.class);
            groupAddInfo(fieldName, val, groupInfo, Objects.requireNonNull(where), clazz);
        });
        return map;
    }

    private static <T, W extends Join<W> & Nested<W, W>> void wheresGroupAddInfo(Class<T> clazz, Object obj, Map<String, GroupInfo<T, W>> map, Field field, String fieldName, Wheres wheres) {
        final String group = Optional.of(Objects.requireNonNull(wheres).group()).filter(StringUtils::isNotBlank)
                .orElse(field.getName());
        final Object val = getFieldValue(obj, field);

        final GroupInfo<T, W> groupInfo = map.computeIfAbsent(group, GroupInfo::new);

        if (wheres.outerJoin()) {
            groupInfo.outerJoin = true;
        }
        if (wheres.innerJoin()){
            groupInfo.innerJoin = true;
        }
        final Where[] value = Objects.requireNonNull(wheres).value();
        for (Where where : value) {
            groupAddInfo(fieldName, val, groupInfo, where, clazz);
        }
    }

    private static Method findGetMethod(Class<?> clazz, String fieldName) {
        return ReflectionUtils.findMethod(
                clazz
                , com.baomidou.mybatisplus.core.toolkit.StringUtils.concatCapitalize("get", fieldName)
        );
    }

    private static Object getFieldValue(Object obj, Field field) {
        field.setAccessible(true);
        return ReflectionUtils.getField(field, obj);
    }

    private static <T, W extends Join<W> & Nested<W, W>> void groupAddInfo(String fieldName, Object finalVal, GroupInfo<T, W> groupInfo, Where where, Class<T> clazz) {
        final boolean join = where.join();
        fieldName = Optional.of(where.column()).filter(StringUtils::isNotBlank).orElse(fieldName);

        String column = com.baomidou.mybatisplus.core.toolkit.StringUtils.camelToUnderline(
                Optional.of(where.column()).filter(StringUtils::isNotBlank).orElse(fieldName)
        );
        final Method getMethod = findGetMethod(clazz, fieldName);
        final WhereInfo<T, W> whereInfo = new WhereInfo<>(column, where.value(), finalVal, clazz, getMethod);

        final ColumnJoinInfo<T, W> columnJoinInfo = groupInfo.map.computeIfAbsent(column, (k) -> new ColumnJoinInfo<>(join));
        columnJoinInfo.join = join || columnJoinInfo.join;
        columnJoinInfo.infoList.add(whereInfo);
    }

    private interface Process<W extends Join<W> & Nested<W, W>> {
        void processWhere(W wrapper);
    }

    private interface WhereJoin {
        default boolean getOuterJoin() {
            return false;
        }
        default boolean getInnerJoin() {
            return false;
        }
    }

    private interface Condition<W extends Join<W> & Nested<W, W>> extends Process<W> {
        boolean getCondition();
    }

    private interface JoinAndCondition<T, W extends Join<W> & Nested<W, W>> extends WhereJoin, Condition<W> {
    }


    private final static class GroupInfo<T, W extends Join<W> & Nested<W, W>> implements JoinAndCondition<W, W> {
        String group;
        boolean outerJoin;
        boolean innerJoin;
        Map<String, ColumnJoinInfo<T, W>> map;

        GroupInfo(String group) {
            this.group = group;
            map = new HashMap<>();
        }



        @Override
        public boolean getOuterJoin() {
            return outerJoin;
        }
        @Override
        public boolean getInnerJoin() {
            return innerJoin;
        }

        @Override
        public boolean getCondition() {
            return map != null && map.values().stream().anyMatch(JoinAndCondition::getCondition);
        }

        @Override
        public void processWhere(W wrapper) {
            if (getOuterJoin()) {
                wrapper.or(getCondition(), innerWrapper -> {
                    map.values().forEach(columnInfo -> {
                        if (getInnerJoin()) {
                            innerWrapper.or(columnInfo::processWhere);
                        }else{
                            columnInfo.processWhere(innerWrapper);
                        }
                    });
                });
            } else {
                wrapper.and(getCondition(), innerWrapper -> {
                    map.values().forEach(columnInfo -> {
                        if (getInnerJoin()) {
                            innerWrapper.or(columnInfo::processWhere);
                        }else {
                            columnInfo.processWhere(innerWrapper);
                        }
                    });
                });
            }
        }
    }

    private final static class ColumnJoinInfo<T, W extends Join<W> & Nested<W, W>> implements JoinAndCondition<T, W> {
        boolean join;
        List<WhereInfo<T, W>> infoList;

        public ColumnJoinInfo(boolean join) {
            this.join = join;
            this.infoList = new ArrayList<>();
        }

        @Override
        public boolean getInnerJoin() {
            return join;
        }

        @Override
        public boolean getCondition() {
            return infoList != null && infoList.stream().anyMatch(Condition::getCondition);
        }


        @Override
        public void processWhere(W wrapper) {
            if (!getCondition()) {
                return;
            }
            if (getInnerJoin()) {
                wrapper.and(innerWrapper ->{
                    infoList.forEach(info -> {
                        innerWrapper.or();
                        info.processWhere(innerWrapper);
                    });
                });
            } else {
                infoList.forEach(info -> {
                    info.processWhere(wrapper);
                });
            }
        }
    }

    private final static class WhereInfo<T, W extends Join<W> & Nested<W, W>> implements Condition<W> {
        String column;
        String symbol;
        Object fieldValue;
        Class<T> clazz;
        Method getMethod;

        public WhereInfo(String column, String symbol, Object fieldValue, Class<T> clazz, Method getMethod) {
            this.column = column;
            this.symbol = symbol;
            this.fieldValue = fieldValue;
            this.clazz = clazz;
            this.getMethod = getMethod;
        }

        @Override
        public String toString() {
            return "WhereInfo{" +
                    "column='" + column + '\'' +
                    ", symbol='" + symbol + '\'' +
                    ", fieldValue=" + fieldValue +
                    ", clazz=" + clazz +
                    ", getMethod=" + getMethod +
                    '}';
        }

        @Override
        public boolean getCondition() {
            if (fieldValue == null) return false;
            if (fieldValue instanceof Collection) return CollectionUtils.isNotEmpty((Collection<?>) fieldValue);
            if (fieldValue instanceof String) return StringUtils.isNotBlank((CharSequence) fieldValue);
            return true;
        }

        @Override
        public void processWhere(W wrapper) {
            whereSwitch((LambdaQueryWrapper) wrapper, this);
        }
    }

    private static <T, W extends Join<W> & Nested<W, W>> void whereSwitch(LambdaQueryWrapper<T> queryWrapper, WhereInfo<T, W> whereInfo) {
        switch (whereInfo.symbol) {
            case "=": {
                queryWrapper.eq(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            case "!=": {
                queryWrapper.ne(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            case "null": {
                queryWrapper.isNull(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo));
                break;
            }
            case "notNull": {
                queryWrapper.isNotNull(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo));
                break;
            }
            case "in": {
                queryWrapper.in(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            case "notIn": {
                queryWrapper.notIn(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            case "like": {
                queryWrapper.like(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            case "like%": {
                queryWrapper.likeRight(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            case "<": {
                queryWrapper.lt(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            case "<=": {
                queryWrapper.le(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            case ">": {
                queryWrapper.gt(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            case ">=": {
                queryWrapper.ge(whereInfo.getCondition(), createSFunction(whereInfo.clazz, whereInfo), whereInfo.fieldValue);
                break;
            }
            default:
        }
    }
}
